<script setup lang="ts">

import {onBeforeRouteLeave, onBeforeRouteUpdate} from "vue-router";
import {router} from "@/router";

onBeforeRouteUpdate((to, from, next) => {
  console.log("beforeRouteUpdate", to, from, next);
})

onBeforeRouteLeave((to, from, next) => {
  const answer = window.confirm("are you sure?");
  console.log("beforeRouteLeave", to, from, next)
  if (answer) {
    return next();
  }
  return false;//阻止离开
})


</script>

<template>
  <el-card header="全局前置守卫" class="mb-4">
    <p>你可以使用 <code>router.beforeEach</code> 注册一个全局前置守卫</p>
    <pre v-highlight><code class="javascript">const router = createRouter({ ... })
router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
})</code></pre>
    当一个导航触发时，全局前置守卫按照创建顺序调用。守卫是异步解析执行，此时导航在所有守卫 resolve 完之前一直处于等待中。
    每个守卫方法接收两个参数：
    <ul>
      <li><code>to</code>: 即将要进入的目标 用一种标准化的方式</li>
      <li><code>from</code>: 当前导航正要离开的路由 用一种标准化的方式</li>
    </ul>
    可以返回的值如下:
    <ul>
      <li><code>false</code>: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮)，那么 URL 地址会重置到
        from 路由对应的地址。
      </li>
      <li>一个路由地址: 通过一个路由地址重定向到一个不同的地址，如同调用 <code>router.push()</code>，且可以传入诸如 <code>replace:
        true</code> 或
        <code>name: 'home'</code> 之类的选项。它会中断当前的导航，同时用相同的 <code>from</code> 创建一个新导航
      </li>
    </ul>
    <pre v-highlight><code class="javascript"> router.beforeEach(async (to, from) => {
   if (
     // 检查用户是否已登录
     !isAuthenticated &&
     // ❗️ 避免无限重定向
     to.name !== 'Login'
   ) {
     // 将用户重定向到登录页面
     return { name: 'Login' }
   }
 })</code></pre>
    <h4>可选的第三个参数 next</h4>
    在之前的 Vue Router 版本中，还可以使用 第三个参数 next 。这是一个常见的错误来源，我们经过 RFC
    讨论将其移除。然而，它仍然是被支持的，这意味着你可以向任何导航守卫传递第三个参数。在这种情况下，确保 next
    在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次，但是只能在所有的逻辑路径都不重叠的情况下，否则钩子永远都不会被解析或报错。这里有一个在用户未能验证身份时重定向到/login的错误用例：
    <pre v-highlight><code class="javascript">// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})</code></pre>
  </el-card>

  <el-card header="全局解析守卫" class="mb-4">
    <p>你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach
      类似，因为它在每次导航时都会触发，不同的是，解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。这里有一个例子，根据路由在元信息中的
      requiresCamera 属性确保用户访问摄像头的权限：</p>
    <pre v-highlight><code class="javascript">router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误，然后取消导航
        return false
      } else {
        // 意料之外的错误，取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})</code></pre>
    <p><code>router.beforeResolve</code> 是获取数据或执行任何其他操作（如果用户无法进入页面时你希望避免执行的操作）的理想位置。
    </p>
  </el-card>

  <el-card class="mb-4" header="全局后置钩子">
    <p>你也可以注册全局后置钩子，然而和守卫不同的是，这些钩子不会接受 next 函数也不会改变导航本身：</p>
    <pre v-highlight><code class="javascript">router.afterEach((to, from) => {
  sendToAnalytics(to.fullPath)
})</code></pre>
  </el-card>
  <el-card header="在守卫内的全局注入" class="mb-4">
    <p>从 Vue 3.3 开始，你可以在导航守卫内使用 inject() 方法。这在注入像 pinia stores 这样的全局属性时很有用。在 <code>app.provide()</code>
      中提供的所有内容都可以在
      <code>router.beforeEach()</code>、<code>router.beforeResolve()</code>、<code>router.afterEach()</code> 内获取到：</p>
    <pre v-highlight><code class="javascript">// main.ts
const app = createApp(App)
app.provide('global', 'hello injections')

// router.ts or main.ts
router.beforeEach((to, from) => {
  const global = inject('global') // 'hello injections'
  // a pinia store
  const userStore = useAuthStore()
  // ...
})</code></pre>
  </el-card>

  <el-card class="mb-4" header="路由独享的守卫">
    <p>你可以直接在路由配置上定义 <code>beforeEnter</code> 守卫：</p>
    <pre v-highlight><code class="javascript">const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]</code></pre>
    <p><code>beforeEnter</code> 守卫 只在进入路由时触发，不会在 params、query 或 hash 改变时触发。例如，从
      <code>/users/2</code> 进入到
      <code>/users/3</code> 或者从
      <code>/users/2#info</code> 进入到
      <code>/users/2#projects</code>。它们只有在 从一个不同的 路由导航时，才会被触发。</p>
    <p>你也可以将一个函数数组传递给 <code>beforeEnter</code>，这在为不同的路由重用守卫时很有用：</p>
    <pre v-highlight><code class="javascript">function removeQueryParams(to) {
  if (Object.keys(to.query).length)
    return { path: to.path, query: {}, hash: to.hash }
}

function removeHash(to) {
  if (to.hash) return { path: to.path, query: to.query, hash: '' }
}

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: [removeQueryParams, removeHash],
  },
  {
    path: '/about',
    component: UserDetails,
    beforeEnter: [removeQueryParams],
  },
]</code></pre>
    <p>当配合嵌套路由使用时，父路由和子路由都可以使用 <code>beforeEnter</code>。如果放在父级路由上，路由在具有相同父级的子路由之间移动时，它不会被触发。例如：
    </p>
    <pre v-highlight><code class="javascript">const routes = [
  {
    path: '/user',
    beforeEnter() {
      // ...
    },
    children: [
      { path: 'list', component: UserList },
      { path: 'details', component: UserDetails },
    ],
  },
]</code></pre>
  </el-card>

  <el-card class="mb-4" header="组件内的守卫">
    <p>最后，你可以在路由组件内直接定义路由导航守卫(传递给路由配置的)</p>
    <p>你可以为路由组件添加以下配置</p>
    <ul>
      <li><code>beforeRouteEnter</code></li>
      <li><code>beforeRouteUpdate</code></li>
      <li><code>beforeRouteLeave</code></li>
    </ul>

    <pre v-highlight><code class="javascript">&lt;script&gt;
export default {
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` ！
    // 因为当守卫执行时，组件实例还没被创建！
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变，但是该组件被复用时调用
    // 举例来说，对于一个带有动态参数的路径 `/users/:id`，在 `/users/1` 和 `/users/2` 之间跳转的时候，
    // 由于会渲染同样的 `UserDetails` 组件，因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候，组件已经挂载好了，导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样，它可以访问组件实例 `this`
  },
}
&lt;/script&gt;</code></pre>

    <pre v-highlight><code class="javascript">onBeforeRouteLeave((to, from, next) => {
  const answer = window.confirm("are you sure?");
  console.log("beforeRouteLeave", to, from, next)
  if (answer) {
    return next();
  }
  return false;//阻止离开
})</code></pre>

    <el-button @click="router.push('/')">返回首页</el-button>

  </el-card>

</template>

<style scoped>

</style>